package ztp

import (
	"errors"
	"gitee.com/sunny-zhangqiang/zcache_new/comm"
	"gitee.com/sunny-zhangqiang/zcache_new/comm/list"
	"gitee.com/sunny-zhangqiang/zcache_new/comm/mcache"
	"net"
	"strconv"
	"sync"
	"sync/atomic"
	"syscall"
	"time"
)

type ClientConn struct {
	Conn
	address    string
	expireTime int64
}

func (cc *ClientConn) IsAlive() bool {
	tc := cc.NetConn.(*net.TCPConn)
	rw, _ := tc.SyscallConn()

	alive := false

	rw.Control(func(fd uintptr) {
		val, err := syscall.GetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_ERROR)
		if err == nil && val == 0 {
			alive = true
		}
	})

	return alive
}

type ztcClientConnMgr struct {
	sync.Mutex
	list            *list.List
	maxIdleConn     int
	idleTimeoutMSec int64
}

func (mgr *ztcClientConnMgr) getConnHelper(address string) *ClientConn {
	mgr.Lock()
	defer mgr.Unlock()

	for e := mgr.list.Front(); e != nil; e = e.Next() {
		cc := e.Value.(*ClientConn)
		if cc.address == address {
			mgr.list.Remove(e)
			return cc
		}
	}

	return nil
}

func (mgr *ztcClientConnMgr) getConn(address string) *ClientConn {
	for i := 0; i < 3; i++ {
		c := mgr.getConnHelper(address)
		if c == nil {
			return nil
		}

		if c.IsAlive() {
			return c
		}

		c.NetConn.Close()
	}

	return nil
}

func (mgr *ztcClientConnMgr) putConn(c *ClientConn) {
	if c.Closed || !c.IsAlive() {
		c.NetConn.Close()
		return
	}

	mgr.Lock()
	defer mgr.Unlock()

	if mgr.list.Len() > mgr.maxIdleConn {
		e := mgr.list.Front()
		if e != nil {
			fc := e.Value.(*ClientConn)
			fc.NetConn.Close()
		}
	}

	c.expireTime = time.Now().UnixMilli() + mgr.idleTimeoutMSec
	mgr.list.PushBack(c)
}

func (mgr *ztcClientConnMgr) clearExpireConn() {
	mgr.Lock()
	defer mgr.Unlock()

	now := time.Now().UnixMilli()

	for {
		e := mgr.list.Front()
		if e == nil {
			return
		}
		fc := e.Value.(*ClientConn)
		if fc.expireTime > now {
			return
		}

		mgr.list.Remove(e)
		fc.NetConn.Close()
	}
}

func (mgr *ztcClientConnMgr) close() {
	mgr.Lock()
	defer mgr.Unlock()

	for {
		e := mgr.list.Front()
		if e == nil {
			return
		}
		fc := e.Value.(*ClientConn)
		mgr.list.Remove(e)
		fc.NetConn.Close()
	}
}

type ZTPClient struct {
	connMgr          [8]ztcClientConnMgr
	connTimeoutMSec  int64
	readTimeoutMSec  int64
	writeTimeoutMSec int64
	quit             int32
}

func sendRequest(c *ClientConn, req *Request, writeTimeoutMSec int64) error {
	if req.Action != "" {
		req.Headers["action"] = req.Action
	}

	if req.Host != "" {
		req.Headers["host"] = req.Host
	}

	if req.TraceID != "" {
		req.Headers["trace-id"] = req.TraceID
	}

	if req.Headers["action"] == "" {
		return errors.New("no action")
	}

	headerLen := 0
	for k, v := range req.Headers {
		headerLen += len(k)
		headerLen += 2 // ": "
		headerLen += len(v)
		headerLen += 2 // "\r\n"
	}

	if headerLen > maxHeaderLen {
		return errors.New("request header is too long")
	}

	off := 0
	buf := mcache.Malloc(maxHeaderLen)
	buf = buf[:headerLen]

	for k, v := range req.Headers {
		copy(buf[off:], k)
		off += len(k)
		copy(buf[off:], ": ")
		off += 2
		copy(buf[off:], v)
		off += len(v)
		copy(buf[off:], "\r\n")
		off += 2
	}

	d := time.Now().Add(time.Millisecond * time.Duration(writeTimeoutMSec))
	c.NetConn.SetReadDeadline(d)
	//defer c.NetConn.SetReadDeadline(time.Time{})

	var lenBuf [4]byte
	be.PutUint32(lenBuf[:4], uint32(headerLen))
	err := WriteN(c.NetConn, lenBuf[:4], 4)
	if err != nil {
		return err
	}

	if headerLen > 0 {
		err = WriteN(c.NetConn, buf, headerLen)
		if err != nil {
			return err
		}
	}

	mcache.Free(buf)

	be.PutUint32(lenBuf[:4], uint32(len(req.Body)))
	err = WriteN(c.NetConn, lenBuf[:4], 4)
	if err != nil {
		return err
	}

	if len(req.Body) > 0 {
		err = WriteN(c.NetConn, req.Body, len(req.Body))
		if err != nil {
			return err
		}
	}

	return nil
}

func parseResponseHeader(resp *Response, buf []byte) error {
	err := parseHeaderBuf(resp.Headers, buf)
	if err != nil {
		return err
	}

	status := resp.Headers["status"]
	if status == "" {
		return errors.New("no response status")
	}

	resp.Status, err = strconv.Atoi(status)
	if err != nil {
		return errors.New("invalid response status, " + status)
	}

	if resp.Status < 100 || resp.Status > 999 {
		return errors.New("invalid response status, " + status)
	}

	return nil
}

func readResponse(c *ClientConn, resp *Response, readTimeoutMSec int64) error {
	d := time.Now().Add(time.Millisecond * time.Duration(readTimeoutMSec))
	c.NetConn.SetWriteDeadline(d)
	//defer c.NetConn.SetWriteDeadline(time.Time{})

	var lenBuf [4]byte
	err := ReadN(c.NetConn, lenBuf[:4], 4)
	if err != nil {
		return err
	}

	headerLen := be.Uint32(lenBuf[:4])
	if headerLen > maxHeaderLen {
		return errors.New("response header is too long")
	}

	headerBuf := mcache.Malloc(maxHeaderLen)
	headerBuf = headerBuf[:headerLen]

	if headerLen > 0 {
		err = ReadN(c.NetConn, headerBuf, int(headerLen))
		if err != nil {
			return err
		}
	}

	err = parseResponseHeader(resp, headerBuf)
	if err != nil {
		return err
	}

	mcache.Free(headerBuf)

	err = ReadN(c.NetConn, lenBuf[:4], 4)
	if err != nil {
		return err
	}

	bodyLen := be.Uint32(lenBuf[:4])
	if bodyLen > maxBodyLen {
		return errors.New("response body is too long")
	}

	bodyBuf := make([]byte, bodyLen)

	if bodyLen > 0 {
		err = ReadN(c.NetConn, bodyBuf, int(bodyLen))
		if err != nil {
			return err
		}
	}

	resp.Body = bodyBuf

	return nil
}

func (cli *ZTPClient) Do(address string, req *Request, resp *Response) error {
	if req.Headers == nil || resp.Headers == nil {
		panic("invalid request or response")
	}

	if atomic.LoadInt32(&cli.quit) == 1 {
		return errors.New("client already quit")
	}

	hash := comm.FnvHash(comm.String2Bytes(address))
	idx := hash % uint64(len(cli.connMgr))
	c := cli.connMgr[idx].getConn(address)

	if c == nil {
		netConn, err := net.DialTimeout("tcp", address,
			time.Duration(cli.connTimeoutMSec)*time.Millisecond)
		if err != nil {
			return errors.New("connect " + address + " failed, err: " + err.Error())
		}

		c = &ClientConn{
			Conn: Conn{
				NetConn: netConn,
			},
			address: address,
		}
	}

	defer func() {
		if c == nil {
			return
		}
		c.NetConn.Close()
	}()

	var err error

	err = sendRequest(c, req, cli.writeTimeoutMSec)
	if err != nil {
		return errors.New("send request failed, err: " + err.Error())
	}

	err = readResponse(c, resp, cli.readTimeoutMSec)
	if err != nil {
		return errors.New("read response failed, err: " + err.Error())
	}

	if resp.Status < 200 && resp.Status > 399 {
		c.Closed = true
	}

	if resp.Headers["connection"] == "closed" {
		c.Closed = true
	}

	if c.Closed {
		// closed in defer function
		return nil
	}

	cli.connMgr[idx].putConn(c)
	c = nil
	return nil
}

func (cli *ZTPClient) checkExpireLoop() {
	for {
		time.Sleep(time.Second)
		if atomic.LoadInt32(&cli.quit) == 1 {
			break
		}

		for i := 0; i < len(cli.connMgr); i++ {
			cli.connMgr[i].clearExpireConn()
		}
	}
}

func (cli *ZTPClient) Close() {
	atomic.StoreInt32(&cli.quit, 1)
	for i := 0; i < len(cli.connMgr); i++ {
		cli.connMgr[i].close()
	}
}

func NewZTPClient(maxIdleConn int, idleTimeoutMSec,
	connTimeoutMSec, readTimeoutMSec, writeTimeoutMSec int64) *ZTPClient {
	cli := &ZTPClient{
		connTimeoutMSec:  connTimeoutMSec,
		readTimeoutMSec:  readTimeoutMSec,
		writeTimeoutMSec: writeTimeoutMSec,
	}

	maxIdleConn /= len(cli.connMgr)

	for i := 0; i < len(cli.connMgr); i++ {
		cli.connMgr[i].list = list.New()
		cli.connMgr[i].maxIdleConn = maxIdleConn
		cli.connMgr[i].idleTimeoutMSec = idleTimeoutMSec
	}

	go cli.checkExpireLoop()

	return cli
}
